#include <crt_util.h>

#if (TARGET_OS == OS_WIN)
int uv_barrier_init(_barrier_t* barrier, unsigned int count)
{
    rc_error(barrier != NULL, S_ERROR);
    int err;
    barrier->n = count;
    barrier->count = 0;

    err = _mutex_init(&barrier->mutex);
    if (err)
        return err;

    err = _sem_init(&barrier->turnstile1, 0);
    if (err)
        goto error2;

    err = _sem_init(&barrier->turnstile2, 1);
    if (err)
        goto error;

    return S_SUCCESS;

error:
    _sem_destroy(&barrier->turnstile1);
error2:
    _mutex_destroy(&barrier->mutex);

    return err;
}
int uv_barrier_destroy(_barrier_t* barrier)
{
    rc_error(barrier != NULL, S_ERROR);
    _sem_destroy(&barrier->turnstile2);
    _sem_destroy(&barrier->turnstile1);
    _mutex_destroy(&barrier->mutex);
    return S_SUCCESS;
}
int uv_barrier_wait(_barrier_t* barrier)
{
    int serial_thread;
    rc_error(barrier != NULL, S_ERROR);

    _mutex_lock(&barrier->mutex);
    if (++barrier->count == barrier->n) {
        _sem_wait(&barrier->turnstile2);
        _sem_post(&barrier->turnstile1);
    }
    _mutex_unlock(&barrier->mutex);

    _sem_wait(&barrier->turnstile1);
    _sem_post(&barrier->turnstile1);

    _mutex_lock(&barrier->mutex);
    serial_thread = (--barrier->count == 0);
    if (serial_thread) {
        _sem_wait(&barrier->turnstile1);
        _sem_post(&barrier->turnstile2);
    }
    _mutex_unlock(&barrier->mutex);

    _sem_wait(&barrier->turnstile2);
    _sem_post(&barrier->turnstile2);
    return serial_thread;
}

#elif (TARGET_OS == OS_POSIX)

int _barrier_init(_barrier_t* barrier, unsigned int count)
{
    rc_error(barrier != NULL, S_ERROR);
    int rc = 0;
    barrier->in = 0;
    barrier->out = 0;
    barrier->threshold = count;

    rc = pthread_mutex_init(&barrier->mutex, NULL);
    if (rc != 0)
        goto error2;

    rc = pthread_cond_init(&barrier->cond, NULL);
    if (rc != 0)
        goto error;

    return S_SUCCESS;

error:
    pthread_mutex_destroy(&barrier->mutex);
error2:
    return rc;
}
int _barrier_destroy(_barrier_t* barrier)
{
    rc_error(barrier != NULL, S_ERROR);
    pthread_mutex_lock(&barrier->mutex);

    while (barrier->out != 0)
        pthread_cond_wait(&barrier->cond, &barrier->mutex);

    if (barrier->in != 0)
        return S_ERROR;

    pthread_mutex_unlock(&barrier->mutex);

    pthread_mutex_destroy(&barrier->mutex);
    pthread_cond_destroy(&barrier->cond);

    return S_SUCCESS;
}
int _barrier_wait(_barrier_t* barrier)
{
    rc_error(barrier != NULL, S_ERROR);
    int last = 0;

    pthread_mutex_lock(&barrier->mutex);

    if (++barrier->in == barrier->threshold) {
        barrier->in = 0;
        barrier->out = barrier->threshold;
        pthread_cond_signal(&barrier->cond);
    } else {
        do {
            pthread_cond_wait(&barrier->cond, &barrier->mutex);
        } while (barrier->in != 0);
    }

    last = (--barrier->out == 0);
    pthread_cond_signal(&barrier->cond);

    pthread_mutex_unlock(&barrier->mutex);

    return last;
}

#elif (TARGET_OS == OS_MACH)

int _barrier_init(_barrier_t* barrier, unsigned int count)
{
    rc_error(barrier != NULL, S_ERROR);
    int rc = 0;
    barrier->in = 0;
    barrier->out = 0;
    barrier->threshold = count;

    rc = pthread_mutex_init(&barrier->mutex, NULL);
    if (rc != 0)
        goto error2;

    rc = pthread_cond_init(&barrier->cond, NULL);
    if (rc != 0)
        goto error;

    return S_SUCCESS;

error:
    pthread_mutex_destroy(&barrier->mutex);
error2:
    return rc;
}
int _barrier_destroy(_barrier_t* barrier)
{
    rc_error(barrier != NULL, S_ERROR);
    pthread_mutex_lock(&barrier->mutex);

    while (barrier->out != 0)
        pthread_cond_wait(&barrier->cond, &barrier->mutex);

    if (barrier->in != 0)
        abort();

    pthread_mutex_unlock(&barrier->mutex);

    pthread_mutex_destroy(&barrier->mutex);
    pthread_cond_destroy(&barrier->cond);

    return S_SUCCESS;
}
int _barrier_wait(_barrier_t* barrier)
{
    rc_error(barrier != NULL, S_ERROR);
    int last = 0;

    pthread_mutex_lock(&barrier->mutex);

    if (++barrier->in == barrier->threshold) {
        barrier->in = 0;
        barrier->out = barrier->threshold;
        pthread_cond_signal(&barrier->cond);
    } else {
        do {
            pthread_cond_wait(&barrier->cond, &barrier->mutex);
        } while (barrier->in != 0);
    }

    last = (--barrier->out == 0);
    pthread_cond_signal(&barrier->cond);

    pthread_mutex_unlock(&barrier->mutex);

    return last;
}

#elif (TARGET_OS == OS_UNIX)

int _barrier_init(_barrier_t* barrier, unsigned int count)
{
    rc_error(barrier != NULL, S_ERROR);
    int rc = 0;
    barrier->in = 0;
    barrier->out = 0;
    barrier->threshold = count;

    rc = pthread_mutex_init(&barrier->mutex, NULL);
    if (rc != 0)
        goto error2;

    rc = pthread_cond_init(&barrier->cond, NULL);
    if (rc != 0)
        goto error;

    return S_SUCCESS;

error:
    pthread_mutex_destroy(&barrier->mutex);
error2:
    return rc;
}
int _barrier_destroy(_barrier_t* barrier)
{
    rc_error(barrier != NULL, S_ERROR);
    pthread_mutex_lock(&barrier->mutex);

    while (barrier->out != 0)
        pthread_cond_wait(&barrier->cond, &barrier->mutex);

    if (barrier->in != 0)
        abort();

    pthread_mutex_unlock(&barrier->mutex);

    pthread_mutex_destroy(&barrier->mutex);
    pthread_cond_destroy(&barrier->cond);

    return S_SUCCESS;
}
int _barrier_wait(_barrier_t* barrier)
{
    rc_error(barrier != NULL, S_ERROR);
    int last = 0;

    pthread_mutex_lock(&barrier->mutex);

    if (++barrier->in == barrier->threshold) {
        barrier->in = 0;
        barrier->out = barrier->threshold;
        pthread_cond_signal(&barrier->cond);
    } else {
        do {
            pthread_cond_wait(&barrier->cond, &barrier->mutex);
        } while (barrier->in != 0);
    }

    last = (--barrier->out == 0);
    pthread_cond_signal(&barrier->cond);

    pthread_mutex_unlock(&barrier->mutex);

    return last;
}

#endif
